#pragma once
#include "logger.hpp"
#include <etcd/Value.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Response.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/SyncClient.hpp>
#include <memory>

////////////////////////////////////////////////////////////////
/// @file etcd.hpp
/// @brief 基于etcd的服务注册与发现组件封装
/// @details 提供服务注册(ServiceRegistry)和服务发现(ServiceDiscovery)功能
///          支持自动续约、服务监听和变更通知
/// @author quan
/// @date 2025-10-18
////////////////////////////////////////////////////////////////

/// @class ServiceRegistry
/// @brief 服务注册类
/// @details 负责将服务信息注册到etcd，并通过租约机制自动续约保持服务在线状态
///          当对象销毁时自动取消续约，服务将从etcd中移除
class ServiceRegistry
{
public:
    /// @brief 构造函数，初始化etcd客户端并创建租约
    /// @param host etcd服务器地址，格式：ip:port（如："127.0.0.1:2379"）
    /// @param ttl 租约存活时间（秒），默认3秒，到期后自动续约
    /// @note ttl建议设置为3-10秒，过短会增加网络负担，过长会延迟服务下线检测
    ServiceRegistry(const std::string &host, int ttl = 3)
        : _client(std::make_shared<etcd::SyncClient>(host)),
          _keep_alive(_client->leasekeepalive(ttl)),
          _lease_id(_keep_alive->Lease()) {}

    /// @brief 析构函数，取消租约续约
    /// @details 当对象销毁时，停止租约续约，关联的服务会在TTL到期后自动从etcd删除
    ~ServiceRegistry() { _keep_alive->Cancel(); }

    /// @brief 注册服务到etcd
    /// @param key 服务键名，建议使用路径格式（如："/service/user-service/node1"）
    /// @param value 服务地址信息，通常为"ip:port"格式（如："192.168.1.100:8080"）
    /// @return true-注册成功，false-注册失败
    /// @note 
    ///   - 注册的键值对会关联到租约，租约到期后自动删除
    ///   - 失败时会通过sdebug输出错误信息
    ///   - 同一个key重复注册会覆盖旧值
    bool regist(const std::string &key, const std::string &value)
    {
        auto response = _client->put(key, value, _lease_id);
        if (response.is_ok())
            return true;
        sdebug("{}-{} 注册失败: {}", key, value, response.error_message());
        return false;
    }

private:
    /// @brief etcd同步客户端
    std::shared_ptr<etcd::SyncClient> _client;
    /// @brief 保活对象
    std::shared_ptr<etcd::KeepAlive> _keep_alive;
    /// @brief 保活对象id
    int64_t _lease_id;
};

/// @class ServiceDiscovery
/// @brief 服务发现类
/// @details 监听etcd指定目录下的服务变更，当服务上线/下线时触发回调通知
///          构造时会获取已存在的所有服务，并通过回调通知
class ServiceDiscovery
{
public:
    /// @brief 服务变更通知回调函数类型
    /// @param key 服务键名（完整路径）
    /// @param value 服务地址信息
    using notify_cb = std::function<void(std::string, std::string)>;

public:
    /// @brief 构造函数，初始化服务发现并开始监听
    /// @param host etcd服务器地址，格式：ip:port（如："127.0.0.1:2379"）
    /// @param basedir 监听的服务根目录，必须以'/'结尾（如："/service/"）
    /// @param online_cb 服务上线回调函数，参数为(服务key, 服务value)，可为nullptr
    /// @param offline_cb 服务下线回调函数，参数为(服务key, 服务value)，可为nullptr
    /// @note 
    ///   - 构造时会立即获取basedir下所有已存在的服务，并触发online_cb
    ///   - 之后会持续监听服务变更，自动调用相应回调
    ///   - 回调函数在Watcher线程中执行，注意线程安全
    ServiceDiscovery(const std::string &host, const std::string &basedir, const notify_cb &online_cb, const notify_cb &offline_cb)
        : _client(std::make_shared<etcd::SyncClient>(host)),
          _watcher(std::make_shared<etcd::Watcher>(*_client, basedir, std::bind(&ServiceDiscovery::callback, this, std::placeholders::_1), true)),
          _online_cb(online_cb), _offline_cb(offline_cb)
    {
        auto response = _client->ls(basedir);
        if (response.is_ok() == false)
            sdebug("获取已有服务失败 {}\n", response.error_message());
        int size = response.keys().size();
        for (int i = 0; i < size; ++i)
            if (online_cb)
                online_cb(response.key(i), response.value(i).as_string());
    }

    /// @brief 析构函数，停止服务监听
    /// @details 取消Watcher的监听，释放资源
    ~ServiceDiscovery()
    {
        // Watcher析构时会自动Cancel,但是为了直观,且明确watcher底层是异步调用,这里手动Cancel一下
        if(!_watcher->Cancelled())
            _watcher->Cancel();
    }

private:
    /// @brief etcd监听事件的内部回调函数
    /// @param response etcd事件响应对象，由Watcher自动传入
    /// @details 解析事件类型（PUT/DELETE），调用对应的用户回调函数
    /// @note 此函数在Watcher的内部线程中执行
    void callback(const etcd::Response &response)
    {
        if (response.is_ok() == false)
        {
            sdebug("收到了一个错误的事件通知：{}\n", response.error_message());
            return;
        }
        for (const auto &e : response.events())
            if (e.event_type() == etcd::Event::EventType::PUT && _online_cb)
                _online_cb(e.kv().key(), e.kv().as_string());
            else if (e.event_type() == etcd::Event::EventType::DELETE_ && _offline_cb)
                _offline_cb(e.prev_kv().key(), e.prev_kv().as_string());
    }

private:
    /// @brief etcd同步客户端
    std::shared_ptr<etcd::SyncClient> _client;
    /// @brief etcd监听器，监听服务变更事件
    std::shared_ptr<etcd::Watcher> _watcher;
    /// @brief 服务上下线通知回调
    notify_cb _online_cb, _offline_cb;
};